import { type IssueSnapshot, type Marker, marker, parseMarker, protocolVersion, sha256, } from "../../core/contracts.js"; import { parseAgentCommand } from "../../core/webhook.js"; import type { GiteaComment, GiteaIssue } from "./types.js"; export function createIssueSnapshot( issue: GiteaIssue, comments: GiteaComment[], botLogin: string, ): IssueSnapshot { const humanComments = comments .filter( (comment) => comment.user.login.toLowerCase() !== botLogin.toLowerCase(), ) .filter((comment) => !parseAgentCommand(comment.body)) .map((comment) => ({ id: comment.id, author: comment.user.login, createdAt: comment.created_at, body: comment.body, })); const canonical = JSON.stringify({ v: protocolVersion, number: issue.number, state: issue.state, title: issue.title, body: issue.body, comments: humanComments.map(({ author, createdAt, body }) => ({ author, createdAt, body, })), }); return { digest: sha256(canonical), title: issue.title, body: issue.body, comments: humanComments, }; } export function findAcceptedPlan( comments: GiteaComment[], botLogin: string, issueNumber: number, ): | { marker: Marker; markdown: string; comment: GiteaComment; } | undefined { const candidates = comments .filter( (comment) => comment.user.login.toLowerCase() === botLogin.toLowerCase(), ) .map((comment) => ({ comment, found: parseMarker(comment.body, "plan"), })) .filter((value): value is { comment: GiteaComment; found: Marker } => Boolean(value.found), ) .filter( (value) => value.found.issue === issueNumber && value.found.status === "accepted", ) .sort((a, b) => b.comment.updated_at.localeCompare(a.comment.updated_at), ); const selected = candidates[0]; if (!selected) return undefined; const header = "## Accepted implementation plan\n\n"; const start = selected.comment.body.indexOf(header); const end = selected.comment.body.lastIndexOf( "\n\n", ); if (start < 0) return undefined; const markdown = selected.comment.body .slice(start + header.length, end < 0 ? undefined : end) .trim(); if ( !selected.found.planDigest || sha256(markdown) !== selected.found.planDigest ) return undefined; return { marker: selected.found, markdown, comment: selected.comment }; } export function renderStatus(input: { marker: Marker; heading: string; detail: string; }): string { return `${marker(input.marker)}\n## ${input.heading}\n\n${input.detail}`; }