Merge pull request #102 from laurentenhoor/feature/101-include-pr-url
feat: include PR/MR URL in DEV completion notifications
This commit is contained in:
@@ -224,6 +224,37 @@ export class GitHubProvider implements TaskManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMergedMRUrl(issueId: number): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const raw = await this.gh([
|
||||||
|
"pr", "list",
|
||||||
|
"--state", "merged",
|
||||||
|
"--json", "number,title,body,url,mergedAt",
|
||||||
|
"--limit", "20",
|
||||||
|
]);
|
||||||
|
const prs = JSON.parse(raw) as Array<{
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
url: string;
|
||||||
|
mergedAt: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const pattern = `#${issueId}`;
|
||||||
|
|
||||||
|
// Find the most recently merged PR that references this issue
|
||||||
|
// PRs are returned in reverse chronological order by default
|
||||||
|
const matchingPr = prs.find(
|
||||||
|
(pr) =>
|
||||||
|
pr.title.includes(pattern) || (pr.body ?? "").includes(pattern),
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchingPr?.url ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async addComment(issueId: number, body: string): Promise<void> {
|
async addComment(issueId: number, body: string): Promise<void> {
|
||||||
// Write body to temp file to preserve newlines
|
// Write body to temp file to preserve newlines
|
||||||
const tempFile = join(tmpdir(), `devclaw-comment-${Date.now()}.md`);
|
const tempFile = join(tmpdir(), `devclaw-comment-${Date.now()}.md`);
|
||||||
|
|||||||
@@ -187,6 +187,38 @@ export class GitLabProvider implements TaskManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMergedMRUrl(issueId: number): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const raw = await this.glab([
|
||||||
|
"mr", "list", "--output", "json", "--state", "merged",
|
||||||
|
]);
|
||||||
|
const mrs = JSON.parse(raw) as Array<{
|
||||||
|
iid: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
web_url: string;
|
||||||
|
merged_at: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const pattern = `#${issueId}`;
|
||||||
|
|
||||||
|
// Find the most recently merged MR that references this issue
|
||||||
|
// Sort by merged_at to get the most recent first
|
||||||
|
const matchingMr = mrs
|
||||||
|
.filter(
|
||||||
|
(mr) =>
|
||||||
|
mr.title.includes(pattern) || (mr.description ?? "").includes(pattern),
|
||||||
|
)
|
||||||
|
.sort((a, b) =>
|
||||||
|
new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
return matchingMr?.web_url ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async addComment(issueId: number, body: string): Promise<void> {
|
async addComment(issueId: number, body: string): Promise<void> {
|
||||||
// Write body to temp file to preserve newlines
|
// Write body to temp file to preserve newlines
|
||||||
const tempFile = join(tmpdir(), `devclaw-comment-${Date.now()}.md`);
|
const tempFile = join(tmpdir(), `devclaw-comment-${Date.now()}.md`);
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ export interface TaskManager {
|
|||||||
/** Check if any merged MR/PR exists for a specific issue. */
|
/** Check if any merged MR/PR exists for a specific issue. */
|
||||||
hasMergedMR(issueId: number): Promise<boolean>;
|
hasMergedMR(issueId: number): Promise<boolean>;
|
||||||
|
|
||||||
|
/** Get the URL of the most recently merged MR/PR for a specific issue. Returns null if not found. */
|
||||||
|
getMergedMRUrl(issueId: number): Promise<string | null>;
|
||||||
|
|
||||||
/** Add a comment to an issue. */
|
/** Add a comment to an issue. */
|
||||||
addComment(issueId: number, body: string): Promise<void>;
|
addComment(issueId: number, body: string): Promise<void>;
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Brief summary for group announcement",
|
description: "Brief summary for group announcement",
|
||||||
},
|
},
|
||||||
|
prUrl: {
|
||||||
|
type: "string",
|
||||||
|
description: "Pull Request or Merge Request URL (optional, auto-detected if not provided)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -65,6 +69,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
|
|||||||
const result = params.result as "done" | "pass" | "fail" | "refine" | "blocked";
|
const result = params.result as "done" | "pass" | "fail" | "refine" | "blocked";
|
||||||
const groupId = params.projectGroupId as string;
|
const groupId = params.projectGroupId as string;
|
||||||
const summary = params.summary as string | undefined;
|
const summary = params.summary as string | undefined;
|
||||||
|
let prUrl = params.prUrl as string | undefined;
|
||||||
const workspaceDir = ctx.workspaceDir;
|
const workspaceDir = ctx.workspaceDir;
|
||||||
|
|
||||||
if (!workspaceDir) {
|
if (!workspaceDir) {
|
||||||
@@ -133,11 +138,34 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
|
|||||||
output.gitPull = `warning: ${(err as Error).message}`;
|
output.gitPull = `warning: ${(err as Error).message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-detect PR/MR URL if not provided
|
||||||
|
if (!prUrl) {
|
||||||
|
try {
|
||||||
|
prUrl = await provider.getMergedMRUrl(issueId) ?? undefined;
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore errors in PR URL detection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await deactivateWorker(workspaceDir, groupId, "dev");
|
await deactivateWorker(workspaceDir, groupId, "dev");
|
||||||
await provider.transitionLabel(issueId, "Doing", "To Test");
|
await provider.transitionLabel(issueId, "Doing", "To Test");
|
||||||
|
|
||||||
output.labelTransition = "Doing → To Test";
|
output.labelTransition = "Doing → To Test";
|
||||||
output.announcement = `✅ DEV done #${issueId}${summary ? ` — ${summary}` : ""}. Moved to QA queue.`;
|
|
||||||
|
// Build announcement with PR URL if available
|
||||||
|
let announcement = `✅ DEV done #${issueId}`;
|
||||||
|
if (summary) {
|
||||||
|
announcement += ` — ${summary}`;
|
||||||
|
}
|
||||||
|
if (prUrl) {
|
||||||
|
announcement += `\n🔗 PR: ${prUrl}`;
|
||||||
|
}
|
||||||
|
announcement += `. Moved to QA queue.`;
|
||||||
|
|
||||||
|
output.announcement = announcement;
|
||||||
|
if (prUrl) {
|
||||||
|
output.prUrl = prUrl;
|
||||||
|
}
|
||||||
|
|
||||||
if (project.autoChain) {
|
if (project.autoChain) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user