Archived
190 lines
4.4 KiB
TypeScript
190 lines
4.4 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import type {
|
|
FeedbackReference,
|
|
PlanDiscussionData,
|
|
PlanReviewData,
|
|
PullReviewData,
|
|
} from "./features/contracts.js";
|
|
|
|
export type {
|
|
FeedbackReference,
|
|
ImplementationFixReply,
|
|
PlanConversationReply,
|
|
PlanDiscussionData,
|
|
PlanReviewData,
|
|
PullReviewData,
|
|
PullReviewFinding,
|
|
PullReviewReply,
|
|
} from "./features/contracts.js";
|
|
export {
|
|
assertImplementationFixReply,
|
|
assertImplementationSummary,
|
|
assertPlanConversationReply,
|
|
assertPlanDraft,
|
|
assertPullReviewReply,
|
|
assertReviewDecision,
|
|
} from "./features/validation.js";
|
|
export const protocolVersion = 1;
|
|
export const planLabel = "agent:plan";
|
|
export const implementLabel = "agent:implement";
|
|
export const generatedLabel = "agent:generated";
|
|
export const blockedLabel = "agent:blocked";
|
|
export const planReadyLabel = "agent:plan-ready";
|
|
export const reviewFixLabel = "agent:fix-review";
|
|
|
|
export function log(
|
|
level: string,
|
|
message: string,
|
|
fields: Record<string, unknown>,
|
|
): string {
|
|
return JSON.stringify({
|
|
level,
|
|
message,
|
|
...fields,
|
|
time: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
export type Mode = "plan" | "implement";
|
|
|
|
export interface PlanData {
|
|
issueDigest: string;
|
|
baseSha: string;
|
|
planDigest: string;
|
|
markdown: string;
|
|
summary: string;
|
|
iterations: number;
|
|
}
|
|
|
|
export interface ImplementationData {
|
|
issueDigest: string;
|
|
planDigest: string;
|
|
branch: string;
|
|
baseBranch: string;
|
|
baseSha: string;
|
|
startingRemoteSha: string | null;
|
|
gitSafetyDigest: string;
|
|
diffDigest: string;
|
|
changedFiles: string[];
|
|
pendingFiles: string[];
|
|
summary: string;
|
|
iterations: number;
|
|
review?: ReviewDecision;
|
|
pullRequestNumber?: number;
|
|
response?: string;
|
|
feedback?: FeedbackReference[];
|
|
}
|
|
|
|
export interface Result {
|
|
version: number;
|
|
mode: Mode;
|
|
status: "success" | "no-changes" | "review-failed" | "failed";
|
|
message: string;
|
|
plan?: PlanData;
|
|
implementation?: ImplementationData;
|
|
discussion?: PlanDiscussionData;
|
|
review?: PlanReviewData | PullReviewData;
|
|
}
|
|
|
|
export interface Marker {
|
|
v: number;
|
|
kind:
|
|
| "status"
|
|
| "plan"
|
|
| "implementation"
|
|
| "pull-request"
|
|
| "conversation"
|
|
| "review";
|
|
issue: number;
|
|
mode?: Mode;
|
|
request?: string;
|
|
status?: string;
|
|
issueDigest?: string;
|
|
baseSha?: string;
|
|
planDigest?: string;
|
|
branch?: string;
|
|
pullRequest?: number;
|
|
}
|
|
|
|
export interface IssueSnapshot {
|
|
digest: string;
|
|
title: string;
|
|
body: string;
|
|
comments: Array<{
|
|
id: number;
|
|
author: string;
|
|
createdAt: string;
|
|
body: string;
|
|
}>;
|
|
}
|
|
|
|
export interface PlanDraft {
|
|
planMarkdown: string;
|
|
summary: string;
|
|
}
|
|
|
|
export interface ReviewDecision {
|
|
verdict: "accept" | "revise";
|
|
findings: string[];
|
|
rationale: string;
|
|
}
|
|
|
|
export interface ImplementationSummary {
|
|
summary: string;
|
|
files: string[];
|
|
}
|
|
|
|
export function requireEnv(name: string): string {
|
|
const value = process.env[name]?.trim();
|
|
if (!value)
|
|
throw new Error(`Required environment variable ${name} is not set`);
|
|
return value;
|
|
}
|
|
|
|
export function sha256(value: string): string {
|
|
return createHash("sha256").update(value).digest("hex");
|
|
}
|
|
|
|
export function marker(value: Marker): string {
|
|
return `<!-- gitea-agent:${JSON.stringify(value)} -->`;
|
|
}
|
|
|
|
export function parseMarker(
|
|
body: string,
|
|
kind?: Marker["kind"],
|
|
): Marker | undefined {
|
|
const match = body.match(/<!-- gitea-agent:(\{[^\n]*\}) -->/);
|
|
if (!match?.[1]) return undefined;
|
|
try {
|
|
const value = JSON.parse(match[1]) as Marker;
|
|
if (
|
|
value.v !== protocolVersion ||
|
|
!value.kind ||
|
|
!Number.isInteger(value.issue)
|
|
)
|
|
return undefined;
|
|
if (kind && value.kind !== kind) return undefined;
|
|
return value;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export const statusMarker = (issue: number, mode: Mode): Marker => ({
|
|
v: protocolVersion,
|
|
kind: "status",
|
|
issue,
|
|
mode,
|
|
});
|
|
|
|
export function formatError(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
if (error.cause === undefined) return error.message;
|
|
const cause = formatError(error.cause);
|
|
return cause && cause !== error.message
|
|
? `${error.message}: ${cause}`
|
|
: error.message;
|
|
}
|
|
return String(error);
|
|
}
|