diff --git a/lib/gitlab.ts b/lib/gitlab.ts index c127c04..efef586 100644 --- a/lib/gitlab.ts +++ b/lib/gitlab.ts @@ -1,4 +1,7 @@ /** + * @deprecated This module is deprecated and kept only for reference. + * Use lib/providers/index.ts with createProvider() for GitLab/GitHub abstraction. + * * GitLab wrapper using glab CLI. * Handles label transitions, issue fetching, and MR verification. */ diff --git a/lib/tools/project-register.ts b/lib/tools/project-register.ts index ef16940..65eef58 100644 --- a/lib/tools/project-register.ts +++ b/lib/tools/project-register.ts @@ -12,7 +12,7 @@ import type { ToolContext } from "../types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { readProjects, writeProjects, emptyWorkerState } from "../projects.js"; -import { resolveRepoPath } from "../gitlab.js"; +import { resolveRepoPath } from "../utils.js"; import { createProvider } from "../providers/index.js"; import { log as auditLog } from "../audit.js"; import { DEV_TIERS, QA_TIERS } from "../tiers.js"; diff --git a/lib/tools/queue-status.ts b/lib/tools/queue-status.ts index b821443..0743c75 100644 --- a/lib/tools/queue-status.ts +++ b/lib/tools/queue-status.ts @@ -7,9 +7,11 @@ 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 { listIssuesByLabel, resolveRepoPath, type StateLabel } from "../gitlab.js"; +import { type StateLabel } from "../issue-provider.js"; +import { createProvider } from "../providers/index.js"; import { log as auditLog } from "../audit.js"; import { detectContext, generateGuardrails } from "../context-guard.js"; +import { resolveRepoPath } from "../utils.js"; export function createQueueStatusTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ @@ -61,7 +63,6 @@ export function createQueueStatusTool(api: OpenClawPluginApi) { ? [groupId] : Object.keys(data.projects); - const glabPath = (api.pluginConfig as Record)?.glabPath as string | undefined; const projects: Array> = []; for (const pid of projectIds) { @@ -69,15 +70,19 @@ export function createQueueStatusTool(api: OpenClawPluginApi) { if (!project) continue; const repoPath = resolveRepoPath(project.repo); - const glabOpts = { glabPath, repoPath }; + const { provider } = createProvider({ + glabPath: (api.pluginConfig as Record)?.glabPath as string | undefined, + ghPath: (api.pluginConfig as Record)?.ghPath as string | undefined, + repoPath, + }); - // Fetch queue counts from GitLab + // Fetch queue counts from issue tracker const queueLabels: StateLabel[] = ["To Improve", "To Test", "To Do"]; const queue: Record> = {}; for (const label of queueLabels) { try { - const issues = await listIssuesByLabel(label, glabOpts); + const issues = await provider.listIssuesByLabel(label); queue[label] = issues.map((i) => ({ id: i.iid, title: i.title })); } catch { queue[label] = []; diff --git a/lib/tools/session-health.ts b/lib/tools/session-health.ts index d207176..b23b336 100644 --- a/lib/tools/session-health.ts +++ b/lib/tools/session-health.ts @@ -8,8 +8,10 @@ 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 { transitionLabel, resolveRepoPath, type StateLabel } from "../gitlab.js"; +import { type StateLabel } from "../issue-provider.js"; +import { createProvider } from "../providers/index.js"; import { log as auditLog } from "../audit.js"; +import { resolveRepoPath } from "../utils.js"; export function createSessionHealthTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ @@ -41,14 +43,17 @@ export function createSessionHealthTool(api: OpenClawPluginApi) { } const data = await readProjects(workspaceDir); - const glabPath = (api.pluginConfig as Record)?.glabPath as string | undefined; const issues: Array> = []; let fixesApplied = 0; for (const [groupId, project] of Object.entries(data.projects)) { const repoPath = resolveRepoPath(project.repo); - const glabOpts = { glabPath, repoPath }; + const { provider } = createProvider({ + glabPath: (api.pluginConfig as Record)?.glabPath as string | undefined, + ghPath: (api.pluginConfig as Record)?.ghPath as string | undefined, + repoPath, + }); for (const role of ["dev", "qa"] as const) { const worker = project[role]; @@ -98,13 +103,13 @@ export function createSessionHealthTool(api: OpenClawPluginApi) { }; if (autoFix) { - // Revert GitLab label + // Revert issue label const revertLabel: StateLabel = role === "dev" ? "To Do" : "To Test"; const currentLabel: StateLabel = role === "dev" ? "Doing" : "Testing"; try { if (worker.issueId) { const primaryIssueId = Number(worker.issueId.split(",")[0]); - await transitionLabel(primaryIssueId, currentLabel, revertLabel, glabOpts); + await provider.transitionLabel(primaryIssueId, currentLabel, revertLabel); issue.labelReverted = `${currentLabel} → ${revertLabel}`; } } catch { diff --git a/lib/tools/task-complete.ts b/lib/tools/task-complete.ts index fd996b5..4f1b643 100644 --- a/lib/tools/task-complete.ts +++ b/lib/tools/task-complete.ts @@ -14,14 +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 { - closeIssue, - getIssue, - reopenIssue, - resolveRepoPath, - transitionLabel, - type StateLabel, -} from "../gitlab.js"; +import { type StateLabel } from "../issue-provider.js"; +import { createProvider } from "../providers/index.js"; import { deactivateWorker, getProject, @@ -30,6 +24,7 @@ import { readProjects, } from "../projects.js"; import type { ToolContext } from "../types.js"; +import { resolveRepoPath } from "../utils.js"; const execFileAsync = promisify(execFile); @@ -111,12 +106,15 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { } const repoPath = resolveRepoPath(project.repo); - const glabOpts = { + const { provider } = createProvider({ glabPath: (api.pluginConfig as Record)?.glabPath as | string | undefined, + ghPath: (api.pluginConfig as Record)?.ghPath as + | string + | undefined, repoPath, - }; + }); const output: Record = { success: true, @@ -140,7 +138,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { } await deactivateWorker(workspaceDir, groupId, "dev"); - await transitionLabel(issueId, "Doing", "To Test", glabOpts); + await provider.transitionLabel(issueId, "Doing", "To Test"); output.labelTransition = "Doing → To Test"; output.announcement = `✅ DEV done #${issueId}${summary ? ` — ${summary}` : ""}. Moved to QA queue.`; @@ -150,7 +148,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { const pluginConfig = api.pluginConfig as | Record | undefined; - const issue = await getIssue(issueId, glabOpts); + const issue = await provider.getIssue(issueId); const chainResult = await dispatchTask({ workspaceDir, agentId: ctx.agentId, @@ -165,11 +163,10 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { fromLabel: "To Test", toLabel: "Testing", transitionLabel: (id, from, to) => - transitionLabel( + provider.transitionLabel( id, from as StateLabel, to as StateLabel, - glabOpts, ), pluginConfig, }); @@ -194,8 +191,8 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { // === QA PASS === if (role === "qa" && result === "pass") { await deactivateWorker(workspaceDir, groupId, "qa"); - await transitionLabel(issueId, "Testing", "Done", glabOpts); - await closeIssue(issueId, glabOpts); + await provider.transitionLabel(issueId, "Testing", "Done"); + await provider.closeIssue(issueId); output.labelTransition = "Testing → Done"; output.issueClosed = true; @@ -205,8 +202,8 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { // === QA FAIL === if (role === "qa" && result === "fail") { await deactivateWorker(workspaceDir, groupId, "qa"); - await transitionLabel(issueId, "Testing", "To Improve", glabOpts); - await reopenIssue(issueId, glabOpts); + await provider.transitionLabel(issueId, "Testing", "To Improve"); + await provider.reopenIssue(issueId); const devWorker = getWorker(project, "dev"); const devModel = devWorker.model; @@ -225,7 +222,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { const pluginConfig = api.pluginConfig as | Record | undefined; - const issue = await getIssue(issueId, glabOpts); + const issue = await provider.getIssue(issueId); const chainResult = await dispatchTask({ workspaceDir, agentId: ctx.agentId, @@ -240,11 +237,10 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { fromLabel: "To Improve", toLabel: "Doing", transitionLabel: (id, from, to) => - transitionLabel( + provider.transitionLabel( id, from as StateLabel, to as StateLabel, - glabOpts, ), pluginConfig, }); @@ -269,7 +265,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { // === QA REFINE === if (role === "qa" && result === "refine") { await deactivateWorker(workspaceDir, groupId, "qa"); - await transitionLabel(issueId, "Testing", "Refining", glabOpts); + await provider.transitionLabel(issueId, "Testing", "Refining"); output.labelTransition = "Testing → Refining"; output.announcement = `🤔 QA REFINE #${issueId}${summary ? ` — ${summary}` : ""}. Awaiting human decision.`; diff --git a/lib/tools/task-create.ts b/lib/tools/task-create.ts index 6b1cf22..658aadf 100644 --- a/lib/tools/task-create.ts +++ b/lib/tools/task-create.ts @@ -13,7 +13,7 @@ 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 { resolveRepoPath } from "../gitlab.js"; +import { resolveRepoPath } from "../utils.js"; import { createProvider } from "../providers/index.js"; import { log as auditLog } from "../audit.js"; import type { StateLabel } from "../issue-provider.js"; diff --git a/lib/tools/task-pickup.ts b/lib/tools/task-pickup.ts index 5d19ef3..30f89f1 100644 --- a/lib/tools/task-pickup.ts +++ b/lib/tools/task-pickup.ts @@ -11,17 +11,13 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import { dispatchTask } from "../dispatch.js"; -import { - getCurrentStateLabel, - getIssue, - resolveRepoPath, - transitionLabel, - type StateLabel, -} from "../gitlab.js"; +import { type StateLabel } from "../issue-provider.js"; +import { createProvider } from "../providers/index.js"; import { selectModel } from "../model-selector.js"; import { getProject, getWorker, readProjects } from "../projects.js"; import type { ToolContext } from "../types.js"; import { detectContext, generateGuardrails } from "../context-guard.js"; +import { resolveRepoPath } from "../utils.js"; export function createTaskPickupTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ @@ -101,15 +97,18 @@ export function createTaskPickupTool(api: OpenClawPluginApi) { // 3. Fetch issue and verify state const repoPath = resolveRepoPath(project.repo); - const glabOpts = { + const { provider } = createProvider({ glabPath: (api.pluginConfig as Record)?.glabPath as | string | undefined, + ghPath: (api.pluginConfig as Record)?.ghPath as + | string + | undefined, repoPath, - }; + }); - const issue = await getIssue(issueId, glabOpts); - const currentLabel = getCurrentStateLabel(issue); + const issue = await provider.getIssue(issueId); + const currentLabel = provider.getCurrentStateLabel(issue); const validLabelsForDev: StateLabel[] = ["To Do", "To Improve"]; const validLabelsForQa: StateLabel[] = ["To Test"]; @@ -160,7 +159,7 @@ export function createTaskPickupTool(api: OpenClawPluginApi) { fromLabel: currentLabel, toLabel: targetLabel, transitionLabel: (id, from, to) => - transitionLabel(id, from as StateLabel, to as StateLabel, glabOpts), + provider.transitionLabel(id, from as StateLabel, to as StateLabel), pluginConfig, }); diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..c206bef --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,13 @@ +/** + * Shared utilities for DevClaw. + */ + +/** + * Resolve the repo path from projects.json repo field (handles ~/). + */ +export function resolveRepoPath(repoField: string): string { + if (repoField.startsWith("~/")) { + return repoField.replace("~", process.env.HOME ?? "/home/lauren"); + } + return repoField; +}