From 4e10b171c1153671b8a2e431ee1a85cff4363520 Mon Sep 17 00:00:00 2001 From: Lauren ten Hoor Date: Mon, 9 Feb 2026 22:38:43 +0800 Subject: [PATCH] refactor: migrate issue provider logic to task manager interface and implement GitHub/GitLab providers --- lib/{providers => task-managers}/github.ts | 6 +++--- lib/{providers => task-managers}/gitlab.ts | 6 +++--- lib/{providers => task-managers}/index.ts | 4 ++-- .../task-manager.ts} | 16 +++++++++++----- lib/tools/project-register.ts | 2 +- lib/tools/queue-status.ts | 4 ++-- lib/tools/session-health.ts | 4 ++-- lib/tools/task-complete.ts | 4 ++-- lib/tools/task-create.ts | 4 ++-- lib/tools/task-pickup.ts | 4 ++-- 10 files changed, 30 insertions(+), 24 deletions(-) rename lib/{providers => task-managers}/github.ts (98%) rename lib/{providers => task-managers}/gitlab.ts (97%) rename lib/{providers => task-managers}/index.ts (94%) rename lib/{issue-provider.ts => task-managers/task-manager.ts} (81%) diff --git a/lib/providers/github.ts b/lib/task-managers/github.ts similarity index 98% rename from lib/providers/github.ts rename to lib/task-managers/github.ts index 3f19b38..cc833cb 100644 --- a/lib/providers/github.ts +++ b/lib/task-managers/github.ts @@ -14,12 +14,12 @@ import { writeFile, unlink } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { - type IssueProvider, + type TaskManager, type Issue, type StateLabel, STATE_LABELS, LABEL_COLORS, -} from "../issue-provider.js"; +} from "./task-manager.js"; const execFileAsync = promisify(execFile); @@ -48,7 +48,7 @@ function toIssue(gh: GhIssue): Issue { }; } -export class GitHubProvider implements IssueProvider { +export class GitHubProvider implements TaskManager { private repoPath: string; constructor(opts: GitHubProviderOptions) { diff --git a/lib/providers/gitlab.ts b/lib/task-managers/gitlab.ts similarity index 97% rename from lib/providers/gitlab.ts rename to lib/task-managers/gitlab.ts index b641b27..3c3bec0 100644 --- a/lib/providers/gitlab.ts +++ b/lib/task-managers/gitlab.ts @@ -10,12 +10,12 @@ import { writeFile, unlink } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { - type IssueProvider, + type TaskManager, type Issue, type StateLabel, STATE_LABELS, LABEL_COLORS, -} from "../issue-provider.js"; +} from "./task-manager.js"; const execFileAsync = promisify(execFile); @@ -23,7 +23,7 @@ export type GitLabProviderOptions = { repoPath: string; }; -export class GitLabProvider implements IssueProvider { +export class GitLabProvider implements TaskManager { private repoPath: string; constructor(opts: GitLabProviderOptions) { diff --git a/lib/providers/index.ts b/lib/task-managers/index.ts similarity index 94% rename from lib/providers/index.ts rename to lib/task-managers/index.ts index a51767b..88441df 100644 --- a/lib/providers/index.ts +++ b/lib/task-managers/index.ts @@ -8,7 +8,7 @@ * Can be overridden with explicit `provider` option. */ import { execFileSync } from "node:child_process"; -import type { IssueProvider } from "../issue-provider.js"; +import type { TaskManager } from "./task-manager.js"; import { GitLabProvider } from "./gitlab.js"; import { GitHubProvider } from "./github.js"; import { resolveRepoPath } from "../utils.js"; @@ -34,7 +34,7 @@ function detectProvider(repoPath: string): "gitlab" | "github" { } export type ProviderWithType = { - provider: IssueProvider; + provider: TaskManager; type: "github" | "gitlab"; }; diff --git a/lib/issue-provider.ts b/lib/task-managers/task-manager.ts similarity index 81% rename from lib/issue-provider.ts rename to lib/task-managers/task-manager.ts index 2f233c3..614866a 100644 --- a/lib/issue-provider.ts +++ b/lib/task-managers/task-manager.ts @@ -1,8 +1,8 @@ /** - * IssueProvider — Abstract interface for issue tracker operations. + * TaskManager — Abstract interface for issue tracker operations. * - * GitLab is the first implementation (via glab CLI). - * Future providers: GitHub (via gh CLI), Jira (via API). + * GitHub (via gh CLI) and GitLab (via glab CLI) are the current implementations. + * Future providers: Jira (via API). * * All DevClaw tools operate through this interface, making it possible * to swap issue trackers without changing tool logic. @@ -41,7 +41,7 @@ export type Issue = { web_url: string; }; -export interface IssueProvider { +export interface TaskManager { /** Create a label if it doesn't exist (idempotent). */ ensureLabel(name: string, color: string): Promise; @@ -75,6 +75,12 @@ export interface IssueProvider { /** Check if any merged MR/PR exists for a specific issue. */ hasMergedMR(issueId: number): Promise; - /** Verify the provider is working (CLI available, auth valid, repo accessible). */ + /** Verify the task manager is working (CLI available, auth valid, repo accessible). */ healthCheck(): Promise; } + +/** + * Compatibility alias for backward compatibility. + * @deprecated Use TaskManager instead. + */ +export type IssueProvider = TaskManager; diff --git a/lib/tools/project-register.ts b/lib/tools/project-register.ts index 98efbf4..cdf423f 100644 --- a/lib/tools/project-register.ts +++ b/lib/tools/project-register.ts @@ -13,7 +13,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { readProjects, writeProjects, emptyWorkerState } from "../projects.js"; import { resolveRepoPath } from "../utils.js"; -import { createProvider } from "../providers/index.js"; +import { createProvider } from "../task-managers/index.js"; import { log as auditLog } from "../audit.js"; import { DEV_TIERS, QA_TIERS } from "../tiers.js"; import { DEFAULT_DEV_INSTRUCTIONS, DEFAULT_QA_INSTRUCTIONS } from "../templates.js"; diff --git a/lib/tools/queue-status.ts b/lib/tools/queue-status.ts index 107c133..4768911 100644 --- a/lib/tools/queue-status.ts +++ b/lib/tools/queue-status.ts @@ -7,8 +7,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import type { ToolContext } from "../types.js"; import { readProjects, getProject } from "../projects.js"; -import { type StateLabel } from "../issue-provider.js"; -import { createProvider } from "../providers/index.js"; +import { type StateLabel } from "../task-managers/task-manager.js"; +import { createProvider } from "../task-managers/index.js"; import { log as auditLog } from "../audit.js"; import { detectContext, generateGuardrails } from "../context-guard.js"; diff --git a/lib/tools/session-health.ts b/lib/tools/session-health.ts index c767874..dd92ca8 100644 --- a/lib/tools/session-health.ts +++ b/lib/tools/session-health.ts @@ -8,8 +8,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import type { ToolContext } from "../types.js"; import { readProjects, updateWorker, getSessionForModel } from "../projects.js"; -import { type StateLabel } from "../issue-provider.js"; -import { createProvider } from "../providers/index.js"; +import { type StateLabel } from "../task-managers/task-manager.js"; +import { createProvider } from "../task-managers/index.js"; import { log as auditLog } from "../audit.js"; export function createSessionHealthTool(api: OpenClawPluginApi) { diff --git a/lib/tools/task-complete.ts b/lib/tools/task-complete.ts index f11bdbc..60325d7 100644 --- a/lib/tools/task-complete.ts +++ b/lib/tools/task-complete.ts @@ -14,8 +14,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import { log as auditLog } from "../audit.js"; import { dispatchTask } from "../dispatch.js"; -import { type StateLabel } from "../issue-provider.js"; -import { createProvider } from "../providers/index.js"; +import { type StateLabel } from "../task-managers/task-manager.js"; +import { createProvider } from "../task-managers/index.js"; import { resolveRepoPath } from "../utils.js"; import { deactivateWorker, diff --git a/lib/tools/task-create.ts b/lib/tools/task-create.ts index fa86d6c..60aa132 100644 --- a/lib/tools/task-create.ts +++ b/lib/tools/task-create.ts @@ -13,9 +13,9 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import type { ToolContext } from "../types.js"; import { readProjects } from "../projects.js"; -import { createProvider } from "../providers/index.js"; +import { createProvider } from "../task-managers/index.js"; import { log as auditLog } from "../audit.js"; -import type { StateLabel } from "../issue-provider.js"; +import type { StateLabel } from "../task-managers/task-manager.js"; const STATE_LABELS: StateLabel[] = [ "Planning", diff --git a/lib/tools/task-pickup.ts b/lib/tools/task-pickup.ts index 8215dd4..af540b8 100644 --- a/lib/tools/task-pickup.ts +++ b/lib/tools/task-pickup.ts @@ -11,8 +11,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import { dispatchTask } from "../dispatch.js"; -import { type StateLabel } from "../issue-provider.js"; -import { createProvider } from "../providers/index.js"; +import { type StateLabel } from "../task-managers/task-manager.js"; +import { createProvider } from "../task-managers/index.js"; import { selectModel } from "../model-selector.js"; import { getProject, getWorker, readProjects } from "../projects.js"; import type { ToolContext } from "../types.js";