From 9bc0513198d39ac502e246722b7b31966ef8e987 Mon Sep 17 00:00:00 2001 From: Lauren ten Hoor Date: Mon, 9 Feb 2026 22:43:01 +0800 Subject: [PATCH] refactor: remove deprecated GitLab wrapper module and related code --- lib/gitlab.ts | 183 ---------------------------------- lib/projects.ts | 12 +++ lib/task-managers/index.ts | 13 ++- lib/tools/project-register.ts | 2 +- lib/tools/task-complete.ts | 2 +- lib/utils.ts | 13 --- 6 files changed, 23 insertions(+), 202 deletions(-) delete mode 100644 lib/gitlab.ts delete mode 100644 lib/utils.ts diff --git a/lib/gitlab.ts b/lib/gitlab.ts deleted file mode 100644 index 002143e..0000000 --- a/lib/gitlab.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @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. - */ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; - -const execFileAsync = promisify(execFile); - -// State labels — each issue has exactly ONE at a time -const STATE_LABELS = [ - "Planning", - "To Do", - "Doing", - "To Test", - "Testing", - "Done", - "To Improve", - "Refining", -] as const; - -export type StateLabel = (typeof STATE_LABELS)[number]; - -type GlabOptions = { - repoPath: string; -}; - -async function glab( - args: string[], - opts: GlabOptions, -): Promise { - const { stdout } = await execFileAsync("glab", args, { - cwd: opts.repoPath, - timeout: 30_000, - }); - return stdout.trim(); -} - -export type GitLabIssue = { - iid: number; - title: string; - description: string; - labels: string[]; - state: string; - web_url: string; -}; - -/** - * Fetch a single issue by ID. - */ -export async function getIssue( - issueId: number, - opts: GlabOptions, -): Promise { - const raw = await glab( - ["issue", "view", String(issueId), "--output", "json"], - opts, - ); - return JSON.parse(raw) as GitLabIssue; -} - -/** - * List issues with a specific label. - */ -export async function listIssuesByLabel( - label: StateLabel, - opts: GlabOptions, -): Promise { - try { - const raw = await glab( - ["issue", "list", "--label", label, "--output", "json"], - opts, - ); - return JSON.parse(raw) as GitLabIssue[]; - } catch { - // glab returns error when no issues found - return []; - } -} - -/** - * Transition an issue from one state label to another. - * Uses --unlabel + --label to ensure only one state label at a time. - */ -export async function transitionLabel( - issueId: number, - from: StateLabel, - to: StateLabel, - opts: GlabOptions, -): Promise { - await glab( - [ - "issue", - "update", - String(issueId), - "--unlabel", - from, - "--label", - to, - ], - opts, - ); -} - -/** - * Close an issue. - */ -export async function closeIssue( - issueId: number, - opts: GlabOptions, -): Promise { - await glab(["issue", "close", String(issueId)], opts); -} - -/** - * Reopen an issue. - */ -export async function reopenIssue( - issueId: number, - opts: GlabOptions, -): Promise { - await glab(["issue", "reopen", String(issueId)], opts); -} - -/** - * Check if the current state label on an issue matches expected. - */ -export function hasStateLabel( - issue: GitLabIssue, - expected: StateLabel, -): boolean { - return issue.labels.includes(expected); -} - -/** - * Get the current state label of an issue (first match from STATE_LABELS). - */ -export function getCurrentStateLabel( - issue: GitLabIssue, -): StateLabel | null { - for (const label of STATE_LABELS) { - if (issue.labels.includes(label)) { - return label; - } - } - return null; -} - -/** - * Check if any merged MR exists for a specific issue. - */ -export async function hasMergedMR( - issueId: number, - opts: GlabOptions, -): Promise { - try { - const raw = await glab( - ["mr", "list", "--output", "json", "--state", "merged"], - opts, - ); - const mrs = JSON.parse(raw) as Array<{ title: string; description: string }>; - const pattern = `#${issueId}`; - return mrs.some( - (mr) => - mr.title.includes(pattern) || (mr.description ?? "").includes(pattern), - ); - } catch { - return false; - } -} - -/** - * 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; -} diff --git a/lib/projects.ts b/lib/projects.ts index e1284c6..bf4135b 100644 --- a/lib/projects.ts +++ b/lib/projects.ts @@ -4,6 +4,7 @@ */ import fs from "node:fs/promises"; import path from "node:path"; +import { homedir } from "node:os"; import { TIER_MIGRATION } from "./tiers.js"; export type WorkerState = { @@ -229,3 +230,14 @@ export async function deactivateWorker( issueId: null, }); } + +/** + * Resolve repo path from projects.json repo field (handles ~/ expansion). + * Uses os.homedir() for cross-platform home directory resolution. + */ +export function resolveRepoPath(repoField: string): string { + if (repoField.startsWith("~/")) { + return repoField.replace("~", homedir()); + } + return repoField; +} diff --git a/lib/task-managers/index.ts b/lib/task-managers/index.ts index 88441df..72d4e58 100644 --- a/lib/task-managers/index.ts +++ b/lib/task-managers/index.ts @@ -11,7 +11,7 @@ import { execFileSync } from "node:child_process"; import type { TaskManager } from "./task-manager.js"; import { GitLabProvider } from "./gitlab.js"; import { GitHubProvider } from "./github.js"; -import { resolveRepoPath } from "../utils.js"; +import { resolveRepoPath } from "../projects.js"; export type ProviderOptions = { provider?: "gitlab" | "github"; @@ -24,7 +24,9 @@ function detectProvider(repoPath: string): "gitlab" | "github" { const url = execFileSync("git", ["remote", "get-url", "origin"], { cwd: repoPath, timeout: 5_000, - }).toString().trim(); + }) + .toString() + .trim(); if (url.includes("github.com")) return "github"; return "gitlab"; @@ -39,9 +41,12 @@ export type ProviderWithType = { }; export function createProvider(opts: ProviderOptions): ProviderWithType { - const repoPath = opts.repoPath ?? (opts.repo ? resolveRepoPath(opts.repo) : null); + const repoPath = + opts.repoPath ?? (opts.repo ? resolveRepoPath(opts.repo) : null); if (!repoPath) { - throw new Error("Either repoPath or repo must be provided to createProvider"); + throw new Error( + "Either repoPath or repo must be provided to createProvider", + ); } const type = opts.provider ?? detectProvider(repoPath); diff --git a/lib/tools/project-register.ts b/lib/tools/project-register.ts index cdf423f..fb1c55d 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 "../utils.js"; +import { resolveRepoPath } from "../projects.js"; import { createProvider } from "../task-managers/index.js"; import { log as auditLog } from "../audit.js"; import { DEV_TIERS, QA_TIERS } from "../tiers.js"; diff --git a/lib/tools/task-complete.ts b/lib/tools/task-complete.ts index 60325d7..d278d1c 100644 --- a/lib/tools/task-complete.ts +++ b/lib/tools/task-complete.ts @@ -16,7 +16,7 @@ import { log as auditLog } from "../audit.js"; import { dispatchTask } from "../dispatch.js"; import { type StateLabel } from "../task-managers/task-manager.js"; import { createProvider } from "../task-managers/index.js"; -import { resolveRepoPath } from "../utils.js"; +import { resolveRepoPath } from "../projects.js"; import { deactivateWorker, getProject, diff --git a/lib/utils.ts b/lib/utils.ts deleted file mode 100644 index c206bef..0000000 --- a/lib/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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; -}